"""In this example we show how to construct an interactive
[Choropleth](ps://en.wikipedia.org/wiki/Choropleth_map) map.

The example is inspired by the article
[Choropleth maps with geopandas, Bokeh and Panel]\
(https://dmnfarrell.github.io/bioinformatics/bokeh-maps)
by [Damian Farrell](https://github.com/dmnfarrell). See also his
[Notebook](https://github.com/dmnfarrell/teaching/blob/master/geo/maps_python.ipynb)

The data is provided by [Our World in Data](https://ourworldindata.org/). Take a look at their web
site if you would like some inspiration for designing beautifull, interactive dashboards.

You can find the source
data on [GitHub](https://github.com/owid/owid-datasets/tree/master/datasets).

"""

import json
import pathlib
from typing import Optional

import geopandas as gpd
import pandas as pd
import streamlit as st
from bokeh.models import ColorBar, GeoJSONDataSource, LinearColorMapper
from bokeh.palettes import brewer  # pylint: disable=no-name-in-module
from bokeh.plotting import figure

FILE_DIR = pathlib.Path.cwd() / "gallery/owid_dashboard"
SHAPEFILE = FILE_DIR / "data/ne_110m_admin_0_countries.shp"
OWIDDATASETS_FILE = FILE_DIR / "data/owid_datasets.csv"

INFO = """

#### Ideas for improvement

- Add tooltips to the map
- Speed up the dashboard by not downloading each dataset from the source.
- Add a seperate *tab* with a line or bar chart of the data
- Add functionality to select between maps generated by Bokeh, HoloViews, HvPlot, Plotly and Vega
in order to compare how they work.
- Add a button to maximize the interactive part of the dashboard.
- Change the year selection to a [player widget](https://panel.pyviz.org/reference/widgets/Player.html#gallery-player).
- Add a button to download an interactive embedding of the plot.

This example uses [geopandas](http://geopandas.org/).
On Windows the easiest is to `conda install geopandas`. If you wan't to `pip install geopandas` on
Windows then please follow the
[using-geopandas-windows](https://geoffboeing.com/2014/09/using-geopandas-windows/) article.
"""


class OwidDashboard:
    """A Dashboard showing the Owid World Data like 'Annual CO2 Emissions'

        Args:
            shape_data (Optional[gpd.geodataframe.GeoDataFrame], optional): The Map shape data.
            Defaults to None.
            owid_data_sets (Optional[pd.DataFrame], optional): A DataFrame listing the available
            datasets. Defaults to None.
    """

    def __init__(
        self,
        shape_data: Optional[gpd.geodataframe.GeoDataFrame] = None,
        owid_data_sets: Optional[pd.DataFrame] = None,
    ):

        if not shape_data:
            self.shape_data = self.get_shape_data()
        else:
            self.shape_data = shape_data

        if not owid_data_sets:
            self.owid_data_sets = self.get_owid_data_sets()
        else:
            self.owid_data_sets = owid_data_sets

        self.dataset_names = list(self.owid_data_sets.index)
        self.dataset_name = self.dataset_names[0]
        self.year_range = (1950, 2018)
        self.year = 2010

    def map_plot(self):
        """The Bokeh Map"""
        return self._map_plot(self.dataset_name, self.year)

    def _map_plot(self, name: str, year: int):
        if name and year:
            shape_data, key = self.get_owid_data(
                self.owid_data_sets, self.shape_data, name=name, year=year
            )
            return self.get_map_plot(shape_data, key, key)
        return None

    @st.cache
    def download_link(self) -> str:
        """A HTML string to enable download of the data

        Returns:
            str: A HTML link
        """
        download_icon = (
            '<img src="https://github.com/MarcSkovMadsen/awesome-streamlit/blob/master/assets/'
            'images/icons/download_icon.png?raw=true" height="16px" width="16px"/>'
        )
        if self.dataset_name:
            download_link = self.owid_data_sets.loc[self.dataset_name].url
        else:
            download_link = ""
        return f'<a href="{download_link}" title="Source Data" download>{download_icon}</a>'

    @staticmethod
    @st.cache
    def get_shape_data() -> gpd.geodataframe.GeoDataFrame:
        """Loads the shape data of the map"""
        shape_data = gpd.read_file(SHAPEFILE)[["ADMIN", "ADM0_A3", "geometry"]]
        shape_data.columns = ["country", "country_code", "geometry"]
        shape_data = shape_data.drop(shape_data.index[159])
        return shape_data

    @staticmethod
    @st.cache
    def get_owid_data_sets() -> pd.DataFrame:
        """The list of Owid data sets

        Returns:
            pd.DataFrame: A DataFrame with columns=["name", "url"] and index=["name"]
        """
        return pd.read_csv(OWIDDATASETS_FILE).set_index("name")

    @staticmethod
    @st.cache
    def get_owid_df(url) -> pd.DataFrame:
        """The DataFrame of data from Owid"""
        return pd.read_csv(url)

    @classmethod
    def get_owid_data(  # pylint: disable=too-many-arguments
        cls,
        owid_data_sets: pd.DataFrame,
        shape_data: gpd.geodataframe.GeoDataFrame,
        name: str,
        year: Optional[int] = None,
        key: Optional[str] = None,
    ) -> gpd.geodataframe.GeoDataFrame:
        """An Owid Data Set combined with the shape_data

        Args:
            owid_data_sets (pd.DataFrame): The list of Owid Data Sets
            shape_data (gpd.geodataframe.GeoDataFrame): The shape data for the map
            name (str): The name of the Owid Data Set to look up.
            year (Optional[int], optional): A year to filter to. Defaults to None.
            key (Optional[str], optional): The name of column containing the values.
            Defaults to None.

        Returns:
            gpd.geodataframe.GeoDataFrame: The Owid Data Sets merged with the shape data
        """
        url = owid_data_sets.loc[name].url
        owid_data = cls.get_owid_df(url)
        if year is not None:
            owid_data = owid_data[owid_data["Year"] == year]
        merged = shape_data.merge(owid_data, left_on="country", right_on="Entity", how="left")

        if key is None:
            key = owid_data.columns[2]
        merged[key] = merged[key].fillna(0)
        return merged, key

    @staticmethod
    def to_geo_json_data_source(data: gpd.geodataframe.GeoDataFrame) -> GeoJSONDataSource:
        """Convert the data to a GeoJSONDataSource

        Args:
            data (gpd.geodataframe.GeoDataFrame): The data

        Returns:
            GeoJSONDataSource: The resulting GeoJson Data
        """
        json_data = json.dumps(json.loads(data.to_json()))
        return GeoJSONDataSource(geojson=json_data)

    @classmethod
    def get_map_plot(
        cls,
        shape_data: gpd.geodataframe.GeoDataFrame,
        value_column: Optional[str] = None,
        title: str = "",
    ):
        """Plot GeoDataFrame as a map


        """
        geosource = cls.to_geo_json_data_source(shape_data)
        palette = brewer["OrRd"][8]
        palette = palette[::-1]
        vals = shape_data[value_column]
        color_mapper = LinearColorMapper(palette=palette, low=vals.min(), high=vals.max())
        color_bar = ColorBar(
            color_mapper=color_mapper,
            label_standoff=8,
            height=20,
            location=(0, 0),
            orientation="horizontal",
        )

        plot = figure(title=title, plot_height=500, tools="", sizing_mode="stretch_width")
        plot.xgrid.grid_line_color = None
        plot.ygrid.grid_line_color = None
        plot.patches(
            "xs",
            "ys",
            source=geosource,
            fill_alpha=1,
            line_width=0.5,
            line_color="black",
            fill_color={"field": value_column, "transform": color_mapper},
        )
        plot.add_layout(color_bar, "below")
        plot.toolbar.logo = None
        return plot

    def view(self):
        """Map dashboard"""
        st.markdown(__doc__)
        self.dataset_name = st.selectbox("Select Data Set", options=self.dataset_names, index=0)
        self.year = st.slider(
            "Select Year",
            min_value=self.year_range[0],
            max_value=self.year_range[1],
            value=self.year,
        )
        st.bokeh_chart(self.map_plot())
        st.markdown(INFO)
        st.markdown(self.download_link(), unsafe_allow_html=True)


OwidDashboard().view()
